/*
 * PhraseDictionary.cpp
 *
 *  Created on: Nov 18, 2012
 *      Author: m
 */

#include "PhraseDictionary.h"


namespace physical
{

const std::ios_base::openmode PhraseDictionary::s_defaultOpenmode =
        std::ios_base::in | std::ios_base::out;

const size_t PhraseDictionary::s_blockSize = 256;

PhraseDictionary::PhraseDictionary() :
        m_file( new FixedSizeBlockFile( s_blockSize ) ),
        m_freeSpaceManager(),
        m_phraseSerializer( new PhraseSerializer )
{
}

PhraseDictionary::PhraseDictionary( const std::string& filename ) :
        m_file( new FixedSizeBlockFile( s_blockSize ) ),
        m_freeSpaceManager(),
        m_phraseSerializer( new PhraseSerializer )
{
    this->open( filename );
}

PhraseDictionary::PhraseDictionary( const std::string& filename,
        std::ios_base::openmode openmode ) :
        m_file( new FixedSizeBlockFile( s_blockSize ) ),
        m_freeSpaceManager(),
        m_phraseSerializer( new PhraseSerializer )
{
    this->open( filename, openmode );
}

PhraseDictionary::~PhraseDictionary()
{
    this->close();
}

size_t PhraseDictionary::add( const Phrase &phrase )
{
    // Pido un id de bloque libre
    auto newPosition = this->getFreePosition();

    Record record( newPosition, phrase );

    // La persisto
    this->saveRecord( record );

    return newPosition;
}

void PhraseDictionary::remove( size_t pos )
{
    this->freeRecord( pos );
}

void PhraseDictionary::update(  size_t position, const Phrase& phrase  )
{
    if ( this->recordExists( position ) == false )
        throw PhraseDoesNotExistException();

    Record record( position, phrase );

    this->saveRecord( record );
}

PhraseDictionary::Record PhraseDictionary::get( size_t position )
{
    auto result = this->loadRecord( position );

    return result;
}

PhraseDictionary::Record PhraseDictionary::getFirst()
{
    auto result = this->loadRecord( this->getNextRecordPosition( 0 ) );

    return result;
}

PhraseDictionary::Record PhraseDictionary::getNext( const Record& record )
{
    auto nextRecordPosition = this->getNextRecordPosition( record.getPosition() );

    auto result = this->loadRecord( nextRecordPosition );

    return result;
}

bool PhraseDictionary::hasNext( const Record& record )
{
    return this->getNextRecordPosition( record.getPosition() ) != 0;
}

void PhraseDictionary::open( const std::string& filename,
        std::ios_base::openmode openmode )
{
    bool truncateFile = openmode & std::ios_base::trunc;

    if ( !this->fileExists( filename ) || truncateFile )
    {
        this->initializeControlData();

        m_file->open( filename.c_str(), openmode );

        this->writeControlData();
    }
    else
    {
        m_file->open( filename.c_str(), openmode );

        this->readControlData();
    }
}

void PhraseDictionary::close()
{
    m_file->close();
}

//******************************************//
//********* Métodos Privados ***************//
//******************************************//

PhraseDictionary::Record PhraseDictionary::loadRecord( size_t pos )
{
    byte block[s_blockSize];

    // Si solo hay un bloque en el archivo, entonces es el bloque de control
    if ( m_file->getBlocksCount() == 1 )
    {
        throw DictionaryIsEmptyException();
    }

    // TODO: pasar esto a loadRecord!!
    if ( !this->recordExists( pos ) )
    {
        throw PhraseDoesNotExistException();
    }

    Record result( pos, Phrase() );

    m_file->readBlock( pos, block );
    m_phraseSerializer->deserialize( block, result.get() );

    return result;
}

void PhraseDictionary::saveRecord( const Record& record )
{
    byte block[s_blockSize];

    // Verifico si la frase se puede persistir ( Tamaño(Frase+Autor) < blockSize )
    if ( this->validatePhraseSize( ((Record)record).get() ) == false )
        throw PhraseIsTooLongException();

    // serializo la frase
    m_phraseSerializer->serialize( ((Record)record).get(), block );

    // lo inserto en el archivo de bloques en esa posición
    m_file->writeBlock( record.getPosition(), block );
}

bool PhraseDictionary::validatePhraseSize( const Phrase& phrase ) const
{
    return m_phraseSerializer->getSize( phrase ) <= s_blockSize;
}

bool PhraseDictionary::recordExists( size_t pos )
{
    return m_freeSpaceManager->isFree( pos ) == false;
}

void PhraseDictionary::freeRecord( size_t pos )
{
    m_freeSpaceManager->free( pos );
    this->writeControlData();
}

size_t PhraseDictionary::getFreePosition()
{
    auto newPosition = m_freeSpaceManager->getFreeBlockNumber();

    m_freeSpaceManager->unfree( newPosition );

    this->writeControlData();

    return newPosition;
}

// Devuelve 0 si no encuentra ninguno ( 0 es inválido porque ahi está la data de control)
size_t PhraseDictionary::getNextRecordPosition( size_t pos )
{
    size_t result = 0;

    // Sacar el hard-coding del máximo de registros!
    for ( auto i = pos + 1; i < 2047; i++ )
    {
        if ( this->recordExists( i ) )
        {
            result = i;
            break;
        }
    }

    return result;
}

void PhraseDictionary::writeControlData()
{
    BitMapFreeSpaceManager* bitMap = ( BitMapFreeSpaceManager* )( m_freeSpaceManager.get() );

    m_file->writeBlock( 0, bitMap->get() );
}

void PhraseDictionary::readControlData()
{
    byte controlDataBuffer[ s_blockSize ];

    m_file->readBlock( 0, controlDataBuffer );
    m_freeSpaceManager.reset( new BitMapFreeSpaceManager( s_blockSize, controlDataBuffer ) );
}

void PhraseDictionary::initializeControlData()
{
    // creo un bitmap con un uno adelante y dsp todos ceros
    m_freeSpaceManager.reset( new BitMapFreeSpaceManager( s_blockSize ) );
    m_freeSpaceManager->unfree( 0 );
}

bool PhraseDictionary::fileExists( const std::string &filename )
{
    std::ifstream file( filename.c_str() );

    return !file.fail();
}

}
